using System.Collections.Generic;
using System.Reflection;
using System.Windows.Forms;
using System.Text;
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using PerformanceDude.MSBuildProfiler.Properties;

namespace PerformanceDude.MSBuildProfiler
{
    public partial class MSBuildProfilerForm : Form
    {
        private ImageList imageList;
        private Build build;

        /// <summary>
        /// Default constructor. Creates a new profiler without loading any snapshot.
        /// </summary>
        public MSBuildProfilerForm()
        {
            InitializeComponent();

            //Resources
            imageList = new ImageList {ColorDepth = ColorDepth.Depth32Bit};
            imageList.Images.Add("Arrow", Resources.arrow);
            imageList.Images.Add("Clock", Resources.clock);
            treeView1.ImageList = imageList;

            treeView1.Nodes.Clear();
        }

        /// <summary>
        /// Creates a new profiler and loading the snapshot specified in the fileName parameter.
        /// </summary>
        /// <param name="fileName"></param>
        public MSBuildProfilerForm(string fileName) : this()
        {
            LoadBuild(fileName);
        }

        /// <summary>
        /// Creates a new profiler and showing the snapshot specified in the build object.
        /// </summary>
        /// <param name="build"></param>
        public MSBuildProfilerForm(Build build) : this()
        {
            this.build = build;
            OpenBuild();
        }

        private void OpenBuild()
        {
            var index = build.IsConsuming() ? 1 : 0;
            TreeNode rootNode = new ItemTreeNode(build, BuildNodeText(build, build.Duration), index, index);
            treeView1.Nodes.Add(rootNode);
            AddNodes(build.Children, rootNode, build.Duration);
            saveToolStripMenuItem.Enabled = true;
            closeToolStripMenuItem.Enabled = true;
        }

        private void AddNodes(IList<Item> items, TreeNode inTreeNode, long totalSum)
        {
            var sortedItems = new List<Item>(items);
            sortedItems.Sort(delegate(Item i1, Item i2)
                                 {
                                     if (i1.Duration < i2.Duration)
                                     {
                                         return 1;
                                     }
                                     if (i1.Duration == i2.Duration)
                                     {
                                         return 0;
                                     }
                                     return -1;
                                 });
            foreach (var o in sortedItems)
            {
                var index = o.IsConsuming() ? 1 : 0;
                var node = new ItemTreeNode(o, BuildNodeText(o, totalSum), index, index);
                inTreeNode.Nodes.Add(node);
                if (o.Children.Count > 0)
                {
                    AddNodes(o.Children, node, totalSum);
                }
            }
        }

        private static string BuildNodeText(Item o, long totalSum)
        {
            var nodeBuilder = new StringBuilder();
            //Calculate percentage
            if (totalSum.Equals(o.Duration))
            {
                nodeBuilder.Append("100");
            }
            else
            {
                double d1 = o.Duration;
                double d2 = totalSum;
                var d = d1/d2;
                var result = d*100;
                nodeBuilder.Append(Math.Round(result, 2));
            }
            nodeBuilder.Append(" % ");
            nodeBuilder.Append(o.ItemName);
            nodeBuilder.Append(" - ");
            nodeBuilder.Append(o.Duration/10000);
            nodeBuilder.Append(" ms");
            return nodeBuilder.ToString();
        }

        #region AboutBox

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var b = new StringBuilder();
            b.Append(AssemblyProduct);
            b.Append(" ");
            b.AppendLine(AssemblyVersion);
            b.AppendLine(AssemblyCopyright);
            b.AppendLine();
            b.AppendLine("http://www.codeplex.com/msbuildprofiler");
            MessageBox.Show(b.ToString(), "About", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        public string AssemblyVersion
        {
            get { return Assembly.GetExecutingAssembly().GetName().Version.ToString(); }
        }

        public string AssemblyProduct
        {
            get
            {
                // Get all Product attributes on this assembly
                var attributes =
                    Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyProductAttribute), false);
                // If there aren't any Product attributes, return an empty string
                return attributes.Length == 0 ? "" : ((AssemblyProductAttribute) attributes[0]).Product;
                // If there is a Product attribute, return its value
            }
        }

        public string AssemblyCopyright
        {
            get
            {
                // Get all Copyright attributes on this assembly
                var attributes =
                    Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyCopyrightAttribute), false);
                // If there aren't any Copyright attributes, return an empty string
                return attributes.Length == 0 ? "" : ((AssemblyCopyrightAttribute) attributes[0]).Copyright;
                // If there is a Copyright attribute, return its value
            }
        }

        #endregion

        private void toolStripMenuItem1_Click(object sender, EventArgs e)
        {
            var selectedNode = treeView1.SelectedNode;
            if (selectedNode != null)
            {
                selectedNode.ExpandAll();
            }
        }

        private void toolStripMenuItem2_Click(object sender, EventArgs e)
        {
            var selectedNode = treeView1.SelectedNode;
            if (selectedNode != null)
            {
                selectedNode.Collapse();
            }
        }

        #region Menu

        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (saveFileDialog1.ShowDialog() != DialogResult.OK) return;
            openToolStripMenuItem.Enabled = false;
            closeToolStripMenuItem.Enabled = false;
            saveToolStripMenuItem.Enabled = false;
            toolStripStatusLabel1.Text = "Saving snapshot";
            var ssh = new SaveSnapshotHolder(build, saveFileDialog1.FileName);
            saveSnapshotWorker.RunWorkerAsync(ssh);
        }

        private void closeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CloseBuild();
        }

        private void toolStripMenuItem3_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog() != DialogResult.OK) return;
            CloseBuild();
            LoadBuild(openFileDialog1.FileName);
        }

        private void exitToolStripMenuItem_Click_1(object sender, EventArgs e)
        {
            Application.Exit();
        }

        #endregion

        private void CloseBuild()
        {
            treeView1.Nodes.Clear();
            build = null;
            saveToolStripMenuItem.Enabled = false;
            closeToolStripMenuItem.Enabled = false;
        }

        private void LoadBuild(string fileName)
        {
            toolStripProgressBar1.Minimum = 0;
            toolStripProgressBar1.Maximum = 100;
            toolStripProgressBar1.Value = 0;
            toolStripProgressBar1.Visible = true;
            toolStripStatusLabel1.Text = "Loading snapshot";
            openToolStripMenuItem.Enabled = false;
            loadSnapshotWorker.RunWorkerAsync(fileName);
        }

        private void OnProgressChanged(object sender, ProgressChangedEventArgs args)
        {
            loadSnapshotWorker.ReportProgress(args.LastProgress);
        }

        private void contextMenuStrip1_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (treeView1.SelectedNode == null)
            {
                e.Cancel = true;
            }
        }

        private void treeView1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                treeView1.SelectedNode = treeView1.GetNodeAt(e.X, e.Y);
            }
        }

        #region drag and drop

        private void treeView1_DragDrop(object sender, DragEventArgs e)
        {
            var files = (string[]) e.Data.GetData(DataFormats.FileDrop);
            CloseBuild();
            LoadBuild(files[0]);
        }

        private void treeView1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                var files = (string[]) e.Data.GetData(DataFormats.FileDrop);
                e.Effect = files.Length > 1 ? DragDropEffects.None : DragDropEffects.Copy;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        #endregion

        #region load snapshot worker

        private void loadSnapshotWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            using (var fileStream = new FileStream((string) e.Argument, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                using (var readProgressStream = new ReadProgressStream(fileStream))
                {
                    readProgressStream.ProgressChanged += OnProgressChanged;
                    const int defaultBufferSize = 4096;
                    using (var bufferedStream = new BufferedStream(readProgressStream, defaultBufferSize))
                    {
                        var formatter = new BinaryFormatter();
                        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
                        var build = (Build) formatter.Deserialize(bufferedStream);
                        AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
                        e.Result = build;
                    }
                }
            }
        }

        private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            try
            {
                var an = AssemblyName.GetAssemblyName(@"MSBuildProfiler.dll");
                var assembly = Assembly.Load(an);
                if (assembly != null)
                    return assembly;
            }
            catch
            {
            }

            return Assembly.GetExecutingAssembly();
        }

        private void loadSnapshotWorker_RunWorkerCompleted(object sender,
                                                           System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            toolStripProgressBar1.Visible = false;
            toolStripStatusLabel1.Text = "";
            openToolStripMenuItem.Enabled = true;
            if (e.Cancelled)
            {
                return;
            }
            if (e.Error != null)
            {
                MessageBox.Show("Error loading snapshot." + e.Error);
                return;
            }
            build = (Build) e.Result;
            OpenBuild();
        }

        private void loadSnapshotWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            toolStripProgressBar1.Value = e.ProgressPercentage;
        }

        #endregion

        #region save snapshot worker

        private class SaveSnapshotHolder
        {
            public readonly Build build;
            public readonly string fileName;

            public SaveSnapshotHolder(Build build, string fileName)
            {
                this.build = build;
                this.fileName = fileName;
            }
        }

        private void saveSnapshotWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            var ssh = (SaveSnapshotHolder) e.Argument;
            using (var fileStream = new FileStream(ssh.fileName, FileMode.Create, FileAccess.Write, FileShare.Write))
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(fileStream, ssh.build);
            }
        }

        private void saveSnapshotWorker_RunWorkerCompleted(object sender,
                                                           System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            openToolStripMenuItem.Enabled = true;
            closeToolStripMenuItem.Enabled = true;
            saveToolStripMenuItem.Enabled = true;
            toolStripStatusLabel1.Text = "";
            if (e.Cancelled)
            {
                return;
            }
            if (e.Error != null)
            {
                MessageBox.Show("Error saving snapshot.");
                return;
            }
            return;
        }

        #endregion
    }

    public class ItemTreeNode : TreeNode
    {
        private readonly Item item;

        public ItemTreeNode(Item item, string text, int imageIndex, int selectedImageIndex)
            : base(text, imageIndex, selectedImageIndex)
        {
            this.item = item;
        }

        public Item Item
        {
            get { return item; }
        }
    }
}